//
//  BKWebViewVC.swift
//  TestDemoSwift
//
//  Created by 王锦发 on 2020/3/28.
//  Copyright © 2020 WJF. All rights reserved.
//

import UIKit
import WebKit
import SystemConfiguration

// MARK: - 屏幕、导航栏、Tabbar尺寸
let kScreenBounds = UIScreen.main.bounds
/// 屏幕大小
let kScreenSize: CGSize = kScreenBounds.size
/// 屏幕宽度
let kScreenWidth: CGFloat = kScreenSize.width
/// 屏幕高度
let kScreenHeight: CGFloat = kScreenSize.height

/// 状态栏默认高度，刘海屏44，普通屏20。
var kStatusBarHeight: CGFloat {
    if #available(iOS 13.0, *) {
        let statusBarHeight = UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
        print("状态栏高度：\(statusBarHeight)")
        return statusBarHeight
    } else {
        let statusBarHeight = UIApplication.shared.statusBarFrame.height
        print("状态栏高度：\(statusBarHeight)")
        return statusBarHeight
    }
}

/// 获取导航栏高度，刘海屏88，普通屏64。
var kNavigationBarHeight: CGFloat {
    return kStatusBarHeight + 44
}

struct WebViewKey {
    static let progress = "estimatedProgress"
    static let title = "title"
}

protocol EnumeratableEnum {
    static var allValues: [Self] { get }
}

/** H5调用App的交互方法名 */
enum BKWebViewJSName: String {
    case popToRootWindow
    case goBack
    case openNewWindow
    case closeWindow
}

/** App调用H5的方法名 */
enum EvaluateJSMethod: String {
    case pageReload // 刷新当前页
    case sessionStorageClear = "sessionStorage.clear" // 清空session
}

extension BKWebViewJSName: EnumeratableEnum {
    static var allValues: [BKWebViewJSName] {
        return [
            .popToRootWindow, .goBack, .openNewWindow, .closeWindow
        ]
    }
}

/** 防止添加js方法时引起的内存泄漏*/
class BKWebViewScriptMessageHanlder: NSObject,WKScriptMessageHandler {
    
    deinit {
        print("BKWebViewScriptMessageHanlder is deinit")
    }
    
    weak var delegate: WKScriptMessageHandler?
    
    init(delegate: WKScriptMessageHandler?) {
        self.delegate = delegate
        super.init()
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        self.delegate?.userContentController(userContentController, didReceive: message)
    }
    
}

protocol BKWebViewDelegate: NSObjectProtocol {
    /** JS交互时JS调用原生方法时收到的消息，以及内容*/
    func runJavaScriptReceiveMessage(message: WKScriptMessage)
}

extension BKWebViewDelegate {
    func runJavaScriptReceiveMessage(message: WKScriptMessage) { }
}

class BKWebViewVC: UIViewController {
    
    // MARK: - deinit
    deinit {
        /** 移除js方法，防止内存泄漏*/
        if webView != nil {
            webView.removeObserver(self, forKeyPath: WebViewKey.progress)
            webView.removeObserver(self, forKeyPath: WebViewKey.title)
            /** 修复pop返回可能会引起的崩溃*/
            webView.scrollView.delegate = nil
        }
        self.removeScriptMessageHandler()
        BKWebViewPool.shared.tryCompactWeakHolders()
        print("[\(NSStringFromClass(type(of: self)))]>>>已被释放")
    }
    
    /** 网页视图*/
    public var webView: BKWebView!
    /** 代理*/
    public weak var delegate: BKWebViewDelegate?
    /** JS交互时JS调用原生方法时收到的消息，以及内容*/
    public var runJavaScriptReceiveMessageBlock: ((WKScriptMessage) -> Void)?
    /** 自动改变标题，根据url的标题*/
    public var autoChangeTitle: Bool = true
    /** 自定义标题*/
    public var customNaviTitle: String = ""
    /** 是否下拉重新加载*/
    public var canDownRefresh: Bool = true
    /** 当前网页加载地址*/
    public var urlString: String = ""
    
    /** 自定义的scheme*/
    private var url_scheme: String = ""
    /** 网页来源标记标签*/
    private var memoLabel: UILabel!
    /** 进度条*/
    private var progressView: BKGradientProgressView!
    /** 容器视图*/
    private var containerView: UIScrollView!
    /** js交互的方法名集合*/
    private var scriptMessageHandlerNames = [BKWebViewJSName]()
    /** wkwebview高度*/
    private var webViewHeight: CGFloat = 0.0
    /** 是否隐藏系统NavigationBar*/
    private var naviBarHidden: Bool = false
    /** 标记是否已经展示过网络开小差*/
    private var isShowedNetWorkError: Bool = false
    
    // MARK: - convenience init
    /// 初始化WKWebView控制器
    /// - Parameters:
    ///   - urlString: 当前网页加载地址
    ///   - type: 标记从什么类型跳转打开
    convenience init(with urlString: String) {
        self.init()
        self.urlString = urlString
    }
    
    // MARK: Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        
        self.loadUI()
        self.loadURL()
        self.showLeftBarButtonItem()
    }

    // MARK: - 加载URL
    private func loadURL() {
        guard let url = URL(string: urlString) else { return }
        if webView.isLoading {
            webView.stopLoading()
        }
        self.loadReq(url)
    }

    /// 请求
    private func loadReq(_ url: URL) {
        let urlReq = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 20.0)
        webView.load(urlReq)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 可能白屏
        if webView.title == nil {
            webView.reload()
        }
    }

    private lazy var refreshControl: UIRefreshControl = {
        let refresh = UIRefreshControl()
        refresh.addTarget(self, action: #selector(wkWebViewReload), for: .valueChanged)
        return refresh
    }()
    
    private lazy var backBarButtonItem: UIBarButtonItem = {
        let barItem = UIBarButtonItem(image: UIImage.init(named: ""), style: .done, target: self, action: #selector(wkWebViewGoback))
        return barItem
    }()
    
    private lazy var closeBarButtonItem: UIBarButtonItem = {
        let barItem = UIBarButtonItem(image: UIImage.init(named: ""), style: .done, target: self, action: #selector(wkWebViewGoback))
        return barItem
    }()


    // MARK: - 加载UI
    private func loadUI() {
        containerView = UIScrollView(frame: view.bounds)
        containerView.backgroundColor = .lightGray
        containerView.showsVerticalScrollIndicator = false
        containerView.showsHorizontalScrollIndicator = false
        containerView.isScrollEnabled = false
        view.addSubview(containerView)
        
        webView = BKWebViewPool.shared.getReusedWebView(for: self)
        webView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight-kNavigationBarHeight)
        self.addScriptMessageHandler()
        
        if #available(iOS 11.0, *) {
            webView.scrollView.contentInsetAdjustmentBehavior = .never
        } else {
            self.automaticallyAdjustsScrollViewInsets = false
        }
        
        webView.uiDelegate = self
        webView.navigationDelegate = self
        webView.isExclusiveTouch = false
        webView.scrollView.delegate = self
        webView.scrollView.decelerationRate = .normal
        webView.scrollView.bouncesZoom = false
        webView.allowsBackForwardNavigationGestures = true
        if canDownRefresh {
            webView.scrollView.refreshControl = refreshControl
        }
        
        webView.addObserver(self, forKeyPath: WebViewKey.progress, options: .new, context: nil)
        webView.addObserver(self, forKeyPath: WebViewKey.title, options: .new, context: nil)
        containerView.addSubview(webView)
        
        memoLabel = UILabel(frame: CGRect(x: 0, y: 20, width: view.frame.width, height: 20))
        memoLabel.textAlignment = .center
        memoLabel.font = .systemFont(ofSize: 12)
        memoLabel.textColor = .gray
        memoLabel.alpha = 0.0 // 1.0
        containerView.addSubview(memoLabel)
        
        progressView = BKGradientProgressView(frame: CGRectMake(0, 0, view.frame.width, 3))
        progressView.backgroundColor = .clear
        progressView.progressCornerRadius = 1.5
        progressView.progressColors = [.blue]
        progressView.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        progressView.alpha = 0.0
        containerView.addSubview(progressView)
    }
    
    // MARK: - KVO监听
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == WebViewKey.progress {
            progressView.alpha = 1.0
            progressView.setProgress(Float(webView.estimatedProgress), animated: true)
            if webView.estimatedProgress >= 1.0 {
                UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseOut, animations: { [weak self] in
                    guard let strongSelf = self else { return }
                    strongSelf.progressView.alpha = 0.0
                }) { [weak self] (_) in
                    guard let strongSelf = self else { return }
                    strongSelf.progressView.setProgress(0, animated: false)
                }
            }
        } else if keyPath == WebViewKey.title {
            if customNaviTitle.isValid {
                self.navigationItem.title = customNaviTitle
            } else {
                let _title = webView.title ?? ""
                if _title.contains("jpg") || _title.contains("png") {
                    self.navigationItem.title = _title.prefixLower(range: ".")
                } else {
                    self.navigationItem.title = webView.title
                }
            }
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
    
}


// MARK: - Selector
extension BKWebViewVC {
    
    @objc private func reloadPage() {
        self.callJSMethod(by: .pageReload)
    }
    
    @objc private func sessionStorageClear() {
        let backList = webView.backForwardList.backList
        if !backList.isEmpty, let item = backList.first {
            webView.go(to: item)
        } else if webView.canGoBack {
            while webView.canGoBack {
                webView.goBack()
            }
        }
        self.callJSMethod(by: .sessionStorageClear)
        webView.reload()
    }
    
    @objc func wkWebViewReload() {
        webView.reload()
        if refreshControl.isRefreshing {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                self.refreshControl.endRefreshing()
            }
        }
    }
    
    @objc func wkWebViewGoback() {
        self.webGoBack()
    }
    
}

// MARK: - Private
extension BKWebViewVC {

    private func addScriptMessageHandler() {
        let jsNames = BKWebViewJSName.allValues
        let msgHandler = BKWebViewScriptMessageHanlder(delegate: self)
        scriptMessageHandlerNames += jsNames
        jsNames.forEach { (name) in
            webView.configuration.userContentController.add(msgHandler, name: name.rawValue)
        }
    }
    
    private func removeScriptMessageHandler() {
        for script in scriptMessageHandlerNames {
            webView.configuration.userContentController.removeScriptMessageHandler(forName: script.rawValue)
        }
    }
    
    private func setWebviewMemo(_ memo: String?) {
        guard let promtMessage = memo, promtMessage.count > 0 else { return }
        if memoLabel != nil {
            memoLabel.text = "此网页由 "+promtMessage+" 提供"
        }
    }
    
    private func showLeftBarButtonItem() {
        if webView.canGoBack {
            navigationItem.leftBarButtonItems = [backBarButtonItem, closeBarButtonItem]
        } else {
            navigationItem.leftBarButtonItem = backBarButtonItem
        }
    }
}

// MARK: - Public
extension BKWebViewVC {
    
    static func initWithURLString(_ urlString: String) -> BKWebViewVC {
        let webView = BKWebViewVC()
        webView.urlString = urlString
        return webView
    }
    
    func callJSMethod(by jsString: EvaluateJSMethod, _ completion: ((Any?, Error?) -> Void)? = nil) {
        let js = "\(jsString.rawValue)()"
        webView.evaluateJavaScript(js, completionHandler: completion)
    }
    
    /** 控制WebView的侧滑返回*/
    func allowBackWithGestures(_ isAllow: Bool) {
        webView.allowsBackForwardNavigationGestures = isAllow
    }
    
    /** 复用WKWebView需要清除历史记录*/
    func clearWkWebViewHistory() {
        webView.evaluateJavaScript("document.body.innerHTML = '';", completionHandler: nil)
    }
    
}

// MARK: - 交互
extension BKWebViewVC {
    
    /// 处理与JS交互的方法
    private func handleWKScriptMessage(name: String, body: Any, completion: ((String?) -> Void)? = nil) {
        let jsName = BKWebViewJSName(rawValue: name)
        switch jsName {
        case .popToRootWindow:   self.popToRootWindow()
        case .goBack:                   self.webGoBack()
        case .openNewWindow:      self.openNewWindow(body as! String)
        case .closeWindow:           self.closeWindow(index: body as? Int)
        default: break
        }
        completion?(nil)
    }
    
}

// MARK: - WebView JS交互
extension BKWebViewVC: WKScriptMessageHandler {
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        self.delegate?.runJavaScriptReceiveMessage(message: message)
        self.runJavaScriptReceiveMessageBlock?(message)
        
        // message.name  方法名
        // message.body  参数值
        print("\n方法名name: \(message.name);\n参数值body: \(message.body)")
        self.handleWKScriptMessage(name: message.name, body: message.body)
    }
    
    func popToRootWindow() {
        self.navigationController?.popToRootViewController(animated: true)
    }
    
    func webGoBack() {
        if webView.canGoBack && BKWebViewPool.shared.justOneVisibleWebView {
            webView.goBack()
        } else if webView.canGoBack {
            if webView.backForwardList.backList.count == 2 {
                self.closeWindow(index: -2)
            } else {
                webView.goBack()
            }
        } else {
            self.clearWkWebViewHistory()
            self.callJSMethod(by: .sessionStorageClear)
            self.popToRootWindow()
        }
    }
    
    func openNewWindow(_ url: String) {
        let vc = BKWebViewVC.initWithURLString(url)
        self.navigationController?.pushViewController(vc, animated: true)
    }
    
    func closeWindow(index: Int?) {
        guard index != nil, let viewCtrls = self.navigationController?.viewControllers else {
            self.clearWkWebViewHistory()
            self.callJSMethod(by: .sessionStorageClear)
            self.popToRootWindow()
            return
        }
        if viewCtrls.count > 1 {
            if let _index = index, let item = webView.backForwardList.item(at: _index) {
                webView.go(to: item)
            } else {
                if webView.canGoBack {
                    webView.goBack()
                } else {
                    self.clearWkWebViewHistory()
                    self.callJSMethod(by: .sessionStorageClear)
                    self.popToRootWindow()
                }
            }
        } else {
            guard let _index = index else {
                if webView.canGoBack {
                    webView.goBack()
                }
                return
            }
            if webView.canGoBack && index == -1 {
                webView.goBack()
            } else {
                if webView.backForwardList.backList.count != abs(_index) && webView.canGoBack {
                    webView.goBack()
                } else {
                    if let item = webView.backForwardList.item(at: _index) {
                        webView.go(to: item)
                    }
                }
            }
        }
    }
    
}

// MARK: - WKNavigationDelegate,WKUIDelegate代理
extension BKWebViewVC: WKNavigationDelegate,WKUIDelegate {
    // 在收到响应后,决定是否跳转
    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        guard let httpResp = navigationResponse.response as? HTTPURLResponse else {
            decisionHandler(.allow)
            return
        }
        let policy: WKNavigationResponsePolicy = httpResp.statusCode == 200 ? .allow : .cancel
        decisionHandler(policy)
    }
    
    // 接收到服务器跳转请求之后再执行
    func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
        
    }
    
    // 当webView需要响应身份验证时调用(如需验证服务器证书)
    func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        let authenticationMethod = challenge.protectionSpace.authenticationMethod
        if authenticationMethod == NSURLAuthenticationMethodServerTrust {
            guard let secTrust = challenge.protectionSpace.serverTrust else { return }
            let credential = URLCredential(trust: secTrust)
            completionHandler(.useCredential, credential)
        }
    }
    
    // 创建一个新的WebView
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        guard let isMainFrame = navigationAction.targetFrame?.isMainFrame else { return nil }
        if !isMainFrame {
            webView.load(navigationAction.request)
        }
        return nil
    }
    
    // 页面开始加载时调用
    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        self.setWebviewMemo(webView.url?.host)
    }
    
    // 当内容开始返回时调用
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        
    }
    
    // 页面加载完成之后调用
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        if autoChangeTitle {
            self.navigationItem.title = webView.title
        }
        if customNaviTitle.isValid {
            self.navigationItem.title = customNaviTitle
        }
        webView.evaluateJavaScript("document.body.scrollHeight") { [weak self] (result, error) in
            self?.webViewHeight = CGFloat((result as? NSNumber)?.doubleValue ?? 0.0)
            print("WKWebView高度: \(self?.webViewHeight ?? 0.0)")
        }
    }
    
    // 页面加载失败时调用
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        
    }
    
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        
    }
    
    // 当WKWebView总体内存占用过大即将白屏的时候
    func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
        webView.reload()
    }
    
    // MARK: - 警告框Alert
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alert = UIAlertController(title: "", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
            completionHandler()
        }))
        self.present(alert, animated: true)
    }
    
    // MARK: - 确认框Confirm
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        let alert = UIAlertController(title: "", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "确定", style: .default, handler: { (_) in
            completionHandler(true)
        }))
        alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (_) in
            completionHandler(false)
        }))
        self.present(alert, animated: true)
    }
    
    // MARK: - 输入框Prompt
    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
        // 根据prompt进行判断执行相应的方法，defaultText为参数值，completionHandler为给js的回调
        print("参数值defaultText: \(defaultText ?? ""); 方法名prompt: \(prompt)")
        self.handleWKScriptMessage(name: prompt, body: defaultText as Any) { (_callback) in
            completionHandler(_callback)
        }
    }
    
}

// MARK: - UIScrollViewDelegate代理
extension BKWebViewVC: UIScrollViewDelegate {
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        guard memoLabel != nil else { return }
        if offsetY < 0 {
            if fabsf(Float(offsetY)) > 50 {
                let alpha = (fabsf(Float(offsetY)) - 50) / 100.00
                if alpha >= 1.0 {
                    memoLabel.alpha = 1.0
                } else {
                    memoLabel.alpha = CGFloat(alpha)
                }
            } else {
                memoLabel.alpha = 0.0
            }
        } else {
            memoLabel.alpha = 0.0
        }
    }
    
}
